﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LightCAD.MathLib.CSG.Mesh;

namespace LightCAD.MathLib.CSG
{
   
    interface IIndex
    {
        int i { get; set; }
    }
    partial class Mesh
    {

        public const int INVALID_ID = -1;
       class TopoEdgePrototype
        {
            public int vid;
            public List<TopoTri> tris = new List<TopoTri>();
            TopoEdgePrototype() { }
            TopoEdgePrototype(int v)
            {
                this.vid = v;
            }

            public static TopoEdgePrototype get(int a, int b, List<List<TopoEdgePrototype>> prototypes)
            {
                int N = prototypes[a].size();
                for (int i = 0; i < N; i++)
                {
                    if (prototypes[a][i].vid == b)
                        return prototypes[a][i];
                }
                prototypes[a].push_back(new TopoEdgePrototype(b));
                return prototypes[a][N];
            }
        };


        public class TopoVert: IIndex
        {
            public int @ref;        // index to actual data
            public object data;       // algorithm specific handle

            public List<TopoTri> tris = new List<TopoTri>();       // triangles this vertex is incident on
            public List<TopoEdge> edges = new List<TopoEdge>();      // edges this vertex is incident on
            public int i { get; set; }
        };

       public class TopoEdge: IIndex
        {
            public object data;       // algorithm specific handle

            public List<TopoVert> verts = new List<TopoVert>().makeSize(2);// [2];   // endpoint vertices
            public List<TopoTri> tris = new List<TopoTri>();       //2 incident triangles
            public int i { get; set; }

        };

        public class TopoTri : IIndex
        {
            public int @ref;        // index to actual data
            public object data;       // algorithm specific handle

            public List<TopoVert> verts = new List<TopoVert>().makeSize(3);// [3];   // vertices of this triangle
            public List<TopoEdge> edges = new List<TopoEdge>().makeSize(3);// [3];   // edges of this triangle

            public int i { get; set; }
            // opposite to the given vertex
        }
       public class TopoCache
        {
            public IterPool<TopoVert> verts = new IterPool<TopoVert>();
            public IterPool<TopoEdge> edges = new IterPool<TopoEdge>();
            public IterPool<TopoTri> tris = new IterPool<TopoTri>();

            public Mesh mesh;

            public TopoCache(Mesh mesh)
            {
                this.mesh = mesh;
                this.init();
            }

            public Block<TopoVert> newVert()
            {
                var @ref = mesh.verts.size();
                mesh.verts.push_back(new CorkVertex());
                var v = verts.alloc(); // cache.verts
                v.datum.@ref = @ref;
                return v;
            }
            public Block<TopoEdge> newEdge()
            {
                return edges.alloc(); // cache.edges
            }
            public Block<TopoTri> newTri()
            {
                var @ref = mesh.tris.size();
                mesh.tris.push_back(new Tri());
                var t = tris.alloc(); // cache.tris
                t.datum.@ref = @ref;
                return t;
            }

            // helpers to release bits and pieces
            public void freeVert(Block<TopoVert> item)
            {
                verts.free(item);
            }
            public void freeVert(TopoVert vert)
            {
                verts.free(vert);
            }
            public void freeEdge(Block<TopoEdge> item)
            {
                edges.free(item);
            }
            public void freeEdge(TopoEdge edge)
            {
                edges.free(edge);
            }
            public void freeTri(Block<TopoTri> item)
            {
                tris.free(item);
            }
            public void freeTri(TopoTri tri)
            {
                tris.free(tri);
            }
            public void deleteTri(TopoTri ditem)
            {
                var item = tris.find(ditem);
                deleteTri(item);
            }

            public void deleteTri(Block<TopoTri> item)
            {
                var tri = item.datum;
                // first, unhook the triangle from its faces
                for (int k = 0; k < 3; k++)
                {
                    var v = tri.verts[k];
                    v.tris.erase(tri);
                    var e = tri.edges[k];
                    e.tris.erase(tri);
                }
                // now, let's check for any edges which no longer border triangles
                for (int k = 0; k < 3; k++)
                {
                    var e = tri.edges[k];
                    if (e.tris.size() == 0)
                    {
                        // delete edge
                        // unhook from vertices
                        var v0 = e.verts[0];
                        v0.edges.erase(e);
                        var v1 = e.verts[1];
                        v1.edges.erase(e);
                        freeEdge(e);
                    }
                }
                // now, let's check for any vertices which no longer border triangles
                for (int k = 0; k < 3; k++)
                {
                    var v = tri.verts[k];
                    if (v.tris.size() == 0)
                    {
                        //freeVert(v);
                    }
                }
                // finally, release the triangle
                //这里释放三角非常慢，因为要寻找Block<T>，此处在C#里无需处理，直接清空即可
               // freeTri(tri);

            }

            // helper to flip triangle orientation
            public void flipTri(TopoTri t)
            {
                Utils.swap(t.verts, 0, 1);
                Utils.swap(t.edges, 0, 1);
                Utils.swap(ref mesh.tris[t.@ref].a, ref mesh.tris[t.@ref].b);
            }

            void init()
            {
                // first lay out vertices
                var temp_verts = new List<Block<TopoVert>>();
                temp_verts.makeSize(mesh.verts.size()); // need temp. reference
                for (int i = 0; i < mesh.verts.size(); i++)
                {
                    var vert = verts.alloc(); // cache.verts.alloc()
                    vert.datum.@ref = i;
                    temp_verts[i] = vert;
                }

                // We need to still do the following
                //  * Generate TopoTris
                //  * Generate TopoEdges
                // ---- Hook up references between
                //  * Triangles and Vertices
                //  * Triangles and Edges
                //  * Vertices and Edges

                // We handle two of these items in a pass over the triangles,
                //  * Generate TopoTris
                //  * Hook up Triangles and Vertices
                // building a structure to handle the edges as we go:
                var edgeacc = new List<List<TopoEdgePrototype>>();
                edgeacc.makeSize(mesh.verts.size(), () => new List<TopoEdgePrototype>());
                for (int i = 0; i < mesh.tris.size(); i++)
                {
                    var tri = tris.alloc(); // cache.tris.alloc()
                    tri.datum.@ref = i;
                    var ref_tri = mesh.tris[i];

                    // triangles <-. verts
                    int[] vids = new int[3];
                    for (int k = 0; k < 3; k++)
                    {
                        int vid = vids[k] = ref_tri[k];
                        tri.datum.verts[k] = temp_verts[vid].datum;
                        temp_verts[vid].datum.tris.push_back(tri.datum);
                    }
                    // then, put these in arbitrary but globally consistent order
                    if (vids[0] > vids[1]) Utils.swap(vids, 0, 1);
                    if (vids[1] > vids[2]) Utils.swap(vids, 1, 2);
                    if (vids[0] > vids[1]) Utils.swap(vids, 0, 1);
                    // and accrue in structure
                    TopoEdgePrototype.get(vids[0], vids[1], edgeacc).tris.push_back(tri.datum);
                    TopoEdgePrototype.get(vids[0], vids[2], edgeacc).tris.push_back(tri.datum);
                    TopoEdgePrototype.get(vids[1], vids[2], edgeacc).tris.push_back(tri.datum);
                }

                // Now, we can unpack the edge accumulation to
                //  * Generate TopoEdges
                //  * Hook up Triangles and Edges
                //  * Hook up Vertices and Edges
                for (int vid0 = 0; vid0 < edgeacc.size(); vid0++)
                {
                    for (int ind = 0; ind != edgeacc[vid0].size(); ++ind)
                    {
                        TopoEdgePrototype proto = edgeacc[vid0][ind];
                        int vid1 = proto.vid;
                        var v0 = temp_verts[vid0];
                        var v1 = temp_verts[vid1];

                        var edge = edges.alloc(); // cache.edges.alloc()
                                                  // edges <-. verts
                        edge.datum.verts[0] = v0.datum;
                        v0.datum.edges.push_back(edge.datum);
                        edge.datum.verts[1] = v1.datum;
                        v1.datum.edges.push_back(edge.datum);
                        // edges <-. tris
                        for (int ind2 = 0; ind2 != proto.tris.size(); ++ind2)
                        {
                            var tri = proto.tris[ind2];
                            if (tri.@ref == 3452)
                            {
                            }
                            edge.datum.tris.push_back(tri);
                            for (int k = 0; k < 3; k++)
                            {
                                if (v0.datum != tri.verts[k] && v1.datum != tri.verts[k])
                                {
                                    tri.edges[k] = edge.datum;
                                    break;
                                }
                            }
                        }
                    }
                }
            }


            public void commit()
            {
                //ENSURE(isValid());

                // record which vertices are live
                var live_verts = new List<bool>();
                live_verts.makeSize(mesh.verts.size(), false);
                for (var vert = verts.getFirst(); vert != null; vert = verts.getNext(vert))
                { // cache.verts
                    live_verts[vert.datum.@ref] = true;
                }

                // record which triangles are live, and record connectivity
                var live_tris = new List<bool>();
                live_tris.makeSize(mesh.tris.size(), false);
                for (var tri = tris.getFirst(); tri != null; tri = tris.getNext(tri))
                {// cache.tris
                    live_tris[tri.datum.@ref] = true;
                    for (int k = 0; k < 3; k++)
                        mesh.tris[tri.datum.@ref][k] = tri.datum.verts[k].@ref;
                }

                // compact the vertices and build a remapping function
                var vmap = new List<int>();
                vmap.makeSize(mesh.verts.size());
                int write = 0;
                for (int read = 0; read < mesh.verts.size(); read++)
                {
                    if (live_verts[read])
                    {
                        vmap[read] = write;
                        mesh.verts[write] = mesh.verts[read];
                        write++;
                    }
                    else
                    {
                        vmap[read] = INVALID_ID;
                    }
                }
                //  mesh.verts.resize(write);

                // rewrite the vertex reference ids
                for (var vert = verts.getFirst(); vert != null; vert = verts.getNext(vert))
                { // cache.verts
                    vert.datum.@ref = vmap[vert.datum.@ref];
                }

                var tmap = new List<int>();
                tmap.makeSize(mesh.tris.size());
                write = 0;
                for (int read = 0; read < mesh.tris.size(); read++)
                {
                    if (live_tris[read])
                    {
                        tmap[read] = write;
                        mesh.tris[write] = mesh.tris[read];
                        for (int k = 0; k < 3; k++)
                            mesh.tris[write][k] = vmap[mesh.tris[write][k]];
                        write++;
                    }
                    else
                    {
                        tmap[read] = INVALID_ID;
                    }
                }
                //  mesh.tris.resize(write);

                // rewrite the triangle reference ids
                for (var tri = tris.getFirst(); tri != null; tri = tris.getNext(tri))
                { // cache.tris
                    tri.datum.@ref = tmap[tri.datum.@ref];
                }
            }



            private bool count<T>(IList<T> contain, T val)
            {
                int c = 0;
                foreach (T t in contain)
                {
                    if (Object.Equals(t, val))
                        c++;
                }
                return c > 0;
            }
            private bool count2<T>(IList<T> arr, T val)
            {
                return (Object.Equals(arr[0], val) ? true : false)
                    | (Object.Equals(arr[1], val) ? true : false);
            }
            private bool count3<T>(IList<T> arr, T val)
            {
                return (Object.Equals(arr[0], val) ? true : false)
                    | (Object.Equals(arr[1], val) ? true : false)
                    | (Object.Equals(arr[2], val) ? true : false);
            }
            public void printf(string format, params object[] args)
            {
                Debug.Print(string.Format(format, args));
            }
            public bool isValid()
            {
                //print();
                List<TopoVert> vaddr = new List<TopoVert>();
                List<TopoEdge> eaddr = new List<TopoEdge>();
                List<TopoTri> taddr = new List<TopoTri>();
                verts.for_each((v) => { vaddr.Add(v); });
                edges.for_each((e) => { eaddr.Add(e); });
                tris.for_each((t) => { taddr.Add(t); });

                // check verts
                verts.for_each((v) =>
                {
                    Utils.ENSURE(v.@ref < mesh.verts.size());
                    // make sure each edge pointer goes somewhere and that
                    // the pointed-to site also points back correctly
                    foreach (var e in v.edges)
                    {
                        Utils.ENSURE(eaddr.counter(e) > 0); // pointer is good
                        Utils.ENSURE(count2(e.verts, v) == true); // back-pointer is good
                    }
                    foreach (var t in v.tris)
                    {
                        Utils.ENSURE(taddr.counter(t) > 0);
                        Utils.ENSURE(count3(t.verts, v) == true);
                    }
                });

                // check edges
                edges.for_each((e) =>
                {
                    // check for non-degeneracy
                    Utils.ENSURE(e.verts[0] != e.verts[1]);
                    for (int k = 0; k < 2; k++)
                    {
                        var v = e.verts[k];
                        Utils.ENSURE(vaddr.counter(v) > 0);
                        Utils.ENSURE(count(v.edges, e) == true);
                    }
                    foreach (var t in e.tris)
                    {
                        Utils.ENSURE(taddr.counter(t) > 0);
                        Utils.ENSURE(count3(t.edges, e) == true);
                    }
                });

                // check triangles
                tris.for_each((t) =>
                {
                    // check for non-degeneracy
                    Utils.ENSURE(t.verts[0] != t.verts[1] && t.verts[1] != t.verts[2]
                                                      && t.verts[0] != t.verts[2]);
                    for (int k = 0; k < 3; k++)
                    {
                        var v = t.verts[k];
                        Utils.ENSURE(vaddr.counter(v) > 0);
                        Utils.ENSURE(count(v.tris, t) == true);

                        var e = t.edges[k];
                        Utils.ENSURE(eaddr.counter(e) == 1);
                        Utils.ENSURE(count(e.tris, t) == true);

                        // also need to ensure that the edges are opposite the
                        // vertices as expected
                        var v0 = e.verts[0];
                        var v1 = e.verts[1];
                        Utils.ENSURE((v0 == t.verts[(k + 1) % 3] && v1 == t.verts[(k + 2) % 3])
                            || (v0 == t.verts[(k + 2) % 3] && v1 == t.verts[(k + 1) % 3]));
                    }
                });

                return true;
            }


        }
    }
}
